//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.

#include <cstdlib>
#include <cstdio>
#include <iostream>

/* ascii code for the escape key */
#define ESCAPE 27
#define PAGE_UP 73
#define PAGE_DOWN 81
#define UP_ARROW 72
#define DOWN_ARROW 80
#define LEFT_ARROW 75
#define RIGHT_ARROW 77

using namespace std;

/* The number of our GLUT window */
int window;
bool keys[256];
bool active;
bool fullscreen = true;

bool light;                              // Lighting ON / OFF
bool lp;                                 // L Pressed?
bool fp;                                 // F Pressed?

GLfloat xrot;                            // X Rotation
GLfloat yrot;                            // Y Rotation
GLfloat xspeed;                          // X Rotation Speed
GLfloat yspeed;                          // Y Rotation Speed
GLfloat z=-5.0f;                         // Depth Into The Screen

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.8f, 1.0f };		// Ambient Light Values
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };     // Diffuse Light Values
GLfloat LightPosition[]= { 0.0f, 0.0f, 5.0f, 1.0f };    // Light Position

GLuint  filter;                          // Which Filter To Use
GLuint  texture[3];                   	 // Storage For One Texture


/* Image type - contains height, width, and data */
typedef struct {
	unsigned long sizeX;
	unsigned long sizeY;
	char *data;
} Image;

char files[1][16] = {
		"Data/crate.bmp",
};


// quick and dirty bitmap loader...for 24 bit bitmaps with 1 plane only.
// See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/BMP.txt for more info.
bool ImageLoad(char *filename, Image *image) {
	FILE *file;
	unsigned long size;                 // size of the image in bytes.
	unsigned long i;                    // standard counter.
	unsigned short int planes;          // number of planes in image (must be 1)
	unsigned short int bpp;             // number of bits per pixel (must be 24)
	char temp;                          // temporary color storage for bgr-rgb conversion.

	// make sure the file is there.
	if ((file = fopen(filename, "rb"))==NULL){
		cout << "File Not Found : " << filename << endl;
		return false;
	}

	// seek through the bmp header, up to the width/height:
	fseek(file, 18, SEEK_CUR);

	// read the width
	if ((i = fread(&image->sizeX, 4, 1, file)) != 1) {
		cout << "Error reading width from " << filename << endl;
		return false;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);

	// read the height
	if ((i = fread(&image->sizeY, 4, 1, file)) != 1) {
		cout << "Error reading height from %s " << filename << endl;
		return false;
	}
	cout << "Height of " << filename << ": " << image->sizeY << endl;

	// calculate the size (assuming 24 bits or 3 bytes per pixel).
	size = image->sizeX * image->sizeY * 3;

	// read the planes
	if ((fread(&planes, 2, 1, file)) != 1) {
		cout << "Error reading planes from " << filename << endl;
		return false;
	}
	if (planes != 1) {
		cout << "Planes from " << filename << " is not 1: " << planes << endl;
		return false;
	}

	// read the bpp
	if ((i = fread(&bpp, 2, 1, file)) != 1) {
		cout << "Error reading bpp from " << filename << endl;
		return false;
	}
	if (bpp != 24) {
		cout << "Bpp from " << filename << " is not 24: " << bpp << endl;
		return false;
	}

	// seek past the rest of the bitmap header.
	fseek(file, 24, SEEK_CUR);

	// read the data.
	image->data = (char *) malloc(size);
	if (image->data == NULL) {
		cout << "Error allocating memory for color-corrected image data" << endl ;
		return false;
	}

	if ((i = fread(image->data, size, 1, file)) != 1) {
		cout << "Error reading image data from " << filename << endl;
		return false;
	}

	for (i=0;i<size;i+=3) { // reverse all of the colors. (bgr -> rgb)
		temp = image->data[i];
		image->data[i] = image->data[i+2];
		image->data[i+2] = temp;
	}

	// we're done.
	return true;
}

// Load Bitmaps And Convert To Textures
bool LoadGLTextures(char *filename) {
	// Load Texture
	Image *image1;

	// allocate space for texture
	image1 = (Image *) malloc(sizeof(Image));
	if (image1 == NULL) {
		cout << "Error allocating space for image" << endl;
		return false;
	}

	if (!ImageLoad(filename, image1)) {
		return false;
	}

	glGenTextures(3, &texture[0]);				// Create Texture

	// Create Nearest Filtered Texture
	glBindTexture(GL_TEXTURE_2D, texture[0]);   // 2d texture (x and y size)
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // less cpu, less quality, more frame per seconds
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	// Create Linear Filtered Texture
	glBindTexture(GL_TEXTURE_2D, texture[1]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	// Create MipMapped Texture
	glBindTexture(GL_TEXTURE_2D, texture[2]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image1->sizeX, image1->sizeY, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	if (image1){
		if (image1->data){
			free(image1->data);
		}
		free(image1);
	}
	return true;
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
bool InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
	LoadGLTextures(files[0]);
	glEnable(GL_TEXTURE_2D);

	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);	// This Will Clear The Background Color To Black
	glClearDepth(1.0);						// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LESS);			// The Type Of Depth Test To Do
	glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
	//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();				// Reset The Projection Matrix

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);

	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);             // Setup The Ambient Light
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);             // Setup The Diffuse Light
	glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);            // Position The Light
	glEnable(GL_LIGHT1);

	return true;
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
	if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
		Height=1;

	glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
	glMatrixMode(GL_MODELVIEW);

	glLoadIdentity();
}

/* The main drawing function. */
void DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Clear The Screen And The Depth Buffer
	glLoadIdentity();                       				// Reset The View

	glTranslatef(0.0f,0.0f,z);                      // Translate Into/Out Of The Screen By z

	glRotatef(xrot,1.0f,0.0f,0.0f);                     // Rotate On The X Axis By xrot
	glRotatef(yrot,0.0f,1.0f,0.0f);                     // Rotate On The Y Axis By yrot

	glBindTexture(GL_TEXTURE_2D, texture[filter]);              // Select A Texture Based On filter

	glBegin(GL_QUADS);                          // Start Drawing Quads// Front Face
	// Front Face
	glNormal3f( 0.0f, 0.0f, 1.0f);                  // Normal Pointing Towards Viewer
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);  // Point 1 (Front)
	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);  // Point 2 (Front)
	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);  // Point 3 (Front)
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);  // Point 4 (Front)
	// Back Face
	glNormal3f( 0.0f, 0.0f,-1.0f);                  // Normal Pointing Away From Viewer
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Back)
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);  // Point 2 (Back)
	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);  // Point 3 (Back)
	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 4 (Back)
	// Top Face
	glNormal3f( 0.0f, 1.0f, 0.0f);                  // Normal Pointing Up
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);  // Point 1 (Top)
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);  // Point 2 (Top)
	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);  // Point 3 (Top)
	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);  // Point 4 (Top)
	// Bottom Face
	glNormal3f( 0.0f,-1.0f, 0.0f);                  // Normal Pointing Down
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Bottom)
	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 2 (Bottom)
	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);  // Point 3 (Bottom)
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);  // Point 4 (Bottom)
	// Right face
	glNormal3f( 1.0f, 0.0f, 0.0f);                  // Normal Pointing Right
	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 1 (Right)
	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);  // Point 2 (Right)
	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);  // Point 3 (Right)
	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);  // Point 4 (Right)
	// Left Face
	glNormal3f(-1.0f, 0.0f, 0.0f);                  // Normal Pointing Left
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Left)
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);  // Point 2 (Left)
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);  // Point 3 (Left)
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);  // Point 4 (Left)
	glEnd();                                // Done Drawing Quads

	xrot+=xspeed;                               // Add xspeed To xrot
	yrot+=yspeed;                               // Add yspeed To yrot

	// since this is double buffered, swap the buffers to display what just got drawn.
	glutSwapBuffers();
}

void switchFullScreen(){
	if (fullscreen){
		glutFullScreen();
	} else {
		glutReshapeWindow(640,480);
		glutPositionWindow(0,0);
	}
	fullscreen = !fullscreen;
}

void specialKeyPressed(int key, int x, int y){
	usleep(100);

	switch(key){
	case GLUT_KEY_F1 :
		switchFullScreen();
		break;

	case GLUT_KEY_PAGE_UP: // move the cube into the distance.
		z-=0.2f;
		break;

	case GLUT_KEY_PAGE_DOWN: // move the cube closer.
		z+=0.2f;
		break;

	case GLUT_KEY_UP: // decrease x rotation speed;
		xspeed-=0.1f;
		break;

	case GLUT_KEY_DOWN: // increase x rotation speed;
		xspeed+=0.1f;
		break;

	case GLUT_KEY_LEFT: // decrease y rotation speed;
		yspeed-=0.1f;
		break;

	case GLUT_KEY_RIGHT: // increase y rotation speed;
		yspeed+=0.1f;
		break;

	default:
		break;
	}
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y)
{
	usleep(100);

	switch(key){
	case ESCAPE :
		cout << "Exit." << endl;
		glutDestroyWindow(window);
		exit(0);
		break;

	case 'l':
	case 'L': // switch the lighting.
		cout << "L/l pressed; light is: " << light << endl;
		light = !light;              // switch the current value of light, between 0 and 1.
		cout << "Light is now: " << light << endl;
		if (!light) {
			glDisable(GL_LIGHTING);
		} else {
			glEnable(GL_LIGHTING);
		}
		break;

	case 'f':
	case 'F': // switch the filter.
		cout << "F/f pressed; filter is: " << filter << endl;
		filter+=1;
		filter = filter % 2;
		cout << "Filter is now: %d\n" << filter << endl;
		break;

	default:
		break;
	}

	if (key == 'L' && !lp){
		lp=true;                // lp Becomes TRUE
		light=!light;               // Toggle Light TRUE/FALSE
	}
}

int main(int argc, char **argv)
{
	/* Initialize GLUT state - glut will take any command line arguments that pertain to it or
     X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */
	glutInit(&argc, argv);

	/* Select type of Display mode:
     Double buffer
     RGBA color
     Alpha components supported
     Depth buffer */
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);

	/* get a 640 x 480 window */
	glutInitWindowSize(640, 480);

	/* the window starts at the upper left corner of the screen */
	glutInitWindowPosition(0, 0);

	/* Open a window */
	window = glutCreateWindow("Crate tutorial");

	/* Register the function to do all our OpenGL drawing. */
	glutDisplayFunc(&DrawGLScene);

	/* Go fullscreen.  This is as soon as possible. */
	glutFullScreen();

	/* Even if there are no events, redraw our gl scene. */
	glutIdleFunc(&DrawGLScene);

	/* Register the function called when our window is resized. */
	glutReshapeFunc(&ReSizeGLScene);

	/* Register the function called when the keyboard is pressed. */
	glutKeyboardFunc(&keyPressed);

	/* Register the function called when special keys (arrows, page down, etc) are pressed. */
	glutSpecialFunc(&specialKeyPressed);

	/* Initialize our window. */
	InitGL(640, 480);

	/* Start Event Processing Engine */
	glutMainLoop();

	return 1;
}

